#!/usr/bin/ruby

[
:util, :rw, :http,
:socket, :erb, :uri, :cgi, :"digest/md5", :base64
].each { |req| require "#{req}" }

Util::debug "TODO:", "multipart save in proper place"
Util::debug "TODO:", "sessions"
Util::debug "TODO:", "figure out wierd mime issues with 404.avi?"

module RedWeb

    class HTTPServer 

        include HTTP
 
        INDEX_PATTERN = "index{.rw,.html,.rhtml,.rb,}"
 
        REDWEB_SCRIPT = "ruby/redweb"
        RUBY_SCRIPT = "ruby/script"
        ERB_SCRIPT = "ruby/erb"
 
        SCRIPT_TYPES = {
            REDWEB_SCRIPT => ["rw"],
            RUBY_SCRIPT => ["rb"],
            ERB_SCRIPT => ["rhtml", "erb"]
        }

	def initialize(root = ".", address="localhost", port = 8080)
            @root = root
            Util::debug "server: #{@root} #{port}"
            SCRIPT_TYPES.each { |type, exts| 
		Mime.add(type, *exts)
            }
            Mime.load_file(root + "/mime.types")
             
            @sock = TCPServer.new(address, port)
        end

        def start
            return if @started
            @started = true
            Util::debug "starting..."
            while @started do
		stream, client = @sock.accept
		Util::debug "incoming request #{client}"
		Thread.new {
                    begin
			until stream.closed?
			    set(:request, HTTPRequest.new(stream))
			    set(:response, HTTPResponse.new)
			    service
			    response.write(stream)
			    stream.close unless request.persistant?
			    Util::debug "done"
			end
                    rescue Exception
                        out = Util::exception($!)
                        temp = "./template/err.rw"
                        err = File.file?(temp) && File.read(temp)
                        out = err.sub("XXXRERRWXXX", out) if err
                        stream << out if stream && !stream.closed?
                        stream.close if stream 
                    end
               }
            end
            @started = false
        end

        def service
            self.response << send(self.request.uri.path)
        end

	def mkhost(host) "#{@root}/#{host}"; end

        def method_missing(name, *args, &block)
            host = header(:host)
            host = host.split(":").first unless File.directory?(mkhost(host))
            host.sub!(/.*?\./, "") || break until File.directory?(mkhost(host)) 
            host = "default" unless File.directory?(mkhost(host))
            rootpath = mkhost(host)
            path = "#{rootpath}"

            parts = name.to_s.split(/\//)

            while item = parts.shift 
                item = URI.unescape(item)
		next if item.empty? || item == ".."
                path << "/#{item}" 
                break unless File.directory?(path)
            end

            dex = "#{path}/#{INDEX_PATTERN}"
            opt = File::FNM_CASEFOLD
	    path = Dir.glob(dex, opt).first if File.directory?(path)

            puts "Requesting: #{path}"
 
            return NOT_FOUND unless path && File.exist?(path)

            request[:header, :root_path] = rootpath
            request[:header, :real_path] = path
            request[:header, :real_dir] = path.sub(/\/[^\/]*?$/, "")
            request[:header, :path] = path.sub(rootpath, "")
            request[:header, :ext_path] = URI.unescape(parts.join('/'))
            request[:header, :uri_path] = request.uri.path

            type = Mime.file_type(path) || RUBY_SCRIPT
	    if type == RUBY_SCRIPT || type == ERB_SCRIPT 
		response.content_type = Mime::DEFAULT_CONTENT_TYPE
            end

	    return case type
		when REDWEB_SCRIPT then 
		    ERB.new(RW::parse(File.read(path))).result(binding)
		when ERB_SCRIPT	then ERB.new(File.read(path)).result(binding)
                when RUBY_SCRIPT then instance_eval(File.read(path))
                else File.new(path)
            end
        end

        [:header, :query_param, :post_param, :attachment].each { |item|
            module_eval("def #{item}(key = nil)
		request[:#{item}, key]
            end")
            module_eval("def #{item}=(key, *values)
                values.each { |value|
                    response[:#{item}, key] = value
                }
            end")
        }

        def request 
	    get(:request)
	end

        def response 
	    get(:response) 
	end

        def get(key) Thread.current[key]; end
        def set(key, value) Thread.current[key] = value; end

        alias :h       :header     
        alias :q       :query_param     
        alias :post    :post_param     

        alias :r    :request     
        alias :req  :request     
        alias :resp :response
        alias :out  :response
    end

    module Params

        include Util::Aliases

        def init_params
            bind_rule(/(?<!\\)_/, "-")
            bind_rule(/\\_/, "_")
        end

        def params(type, key = nil)
            type = "#{type}".downcase if type
            key = resolve(key) if key

            @types ||= Hash.new
            return @types unless key || type

            @types[type] ||= Util::Collections::Multi.new if type

            params = @types[type]

	    return params unless key
	    return params[key] if type

	    all = Array.new
	    @types.each { |t, map| all.concat(map[key]) if map.key?(key) }
	    return all
        end

        def each(type = nil, &block)
           params(type).each(&block) 
        end

        def merge(type, all)
            pms = params(type)
            all.each { |k, v| pms[resolve(k)] << v if k }
        end

        def [](type, key = nil) 
            param = params(type, key)
            return param.first if key
            param
        end

        def []=(type, key, value) set(type, key, value) end

        def get(type, key) params(type, key).first; end

        def add(type, key, value)
            params(type, key).push(value)
        end

        def set(type, key, value)
            pms = params(type)
            pms[resolve(key)] = [value]
        end

        [:header, :query_param, :post_param, :attachment].each { |item|
            module_eval("def #{item}(key, value = nil)
                set('#{item}', key, value) if value
                get('#{item}', key)
            end")
            module_eval("def #{item}s(key, *values)
                values.each { |value| add('#{item}', key, value) if value } if values
                params('#{item}', key)
            end")
            module_eval("def each_#{item}(&block)
                each('#{item}') { |name, items|
                    items.each { |item|
p                       yield(name, item)
                    }
                } if block_given?
                params('#{item}')
            end")
        }

        def method_missing(name, *args, &block)
            name = "#{name}"

            type, value = args.shift, args.shift
            value, type = type, :post_param unless value

            ret = case name
                when /\?$/ then get(value, name[0 ... -1])
                when /^get_/ then get(value, name[4 ... name.length])
                when /=$/ then set(type, name[0 ... -1], value)
                when /^set_/ then set(type, name[4 ... name.length], value)
            end
            return ret if ret
            return set(type, name, value) if type && value && value != :post_param

            all = Array.new
	    key = resolve(name)
	    params(nil, key).each { |value|
		all << if block then yield(key, value) else value end
	    }
            !all.empty? &&  all
        end
    end

    class HTTPResponse < String

	STREAM_LIMIT = 50 * 1024

        include Params
        include HTTP

	def initialize
            init_params
            server = "RedWeb 1.0"
            date = CGI::rfc1123_date(Time.now) 
            connection = "keep_alive"
	end

        def clear
            slice!(0..length) unless empty?
        end

        def <<(var)
            case var
                when Array then var.each { |v| self << v }
                when Hash then var.each { |k,v| self.set(:values, k, v) }
                when Status then @status = var
                when File then if File.size(var.path) >  STREAM_LIMIT
			attachment(var.path, var)
			conent_type = Mime.file_type(var)
		    else
			var.rewind
			concat(var.read)
			conent_type = Mime.file_type(var)
		    end
                else concat(var)
	    end if var
        end

        def header_out
            @status ||= OK
            out = "HTTP/1.1 #{@status}\n"
            used = Array.new
            each(:header) { |key, values|
                key = key.split('-').each { |w| w.capitalize!}.join('-')
                out << "#{key} : #{values.join(', ')}#{EOL}" 
                used << key
            }
            {"Content-Type" => Mime::DEFAULT_CONTENT_TYPE,
	     "Content-Length" => length,
	     "Content-Language" => "en",
	     "Content-MD5" => if !self.empty? && md5? 
		Base64.encode64(Digest::MD5.new.update(self).digest)
	     end
            }.each { |key, value|
		out << "#{key} : #{value}#{EOL}" unless !value || used.include?(key)
	    }
            out
        end

        def write(stream)
	    stream.write(header_out)
	    stream.write(EOL)
  
	    if self.empty? 
		file = "#{@status.code}.html"
                out = File.exist?(file) ? File.new(file, "r") : @status.to_s
		conent_type = Mime::DEFAULT_CONTENT_TYPE
                self << out
            end unless @status == OK

	    params(:values, nil).each { |key, value|
		gsub!(key, value)
	    }

	    stream.write(self)

            bytes = ""
           
            each(:attachment) { |name, files|
                puts "Attached: #{name}"
                files.each { |file| 
		    file.rewind
		    stream.write(bytes) while file.read(STREAM_LIMIT, bytes)
                }
            }

        end
    end

    class HTTPRequest 

        include Params
        include Multipart

	attr_reader :method, :protocol, :uri

	def initialize(stream)
            init_params
            bind_alias(/.*cookie.?/, "cookie")
            bind_alias(/http_(.*)/)
	    @cookies = Util::Collections::Multi.new
	    line = stream.readline 
	    @method, uri, @protocol = line.split
	    @uri = URI.parse(uri)

	    while (line = stream.readline) != nil do
		if line.strip.length == 0 then
		    break
		else
		    name, *value = line.split(':')
		    name = name.downcase
                    value = value.join(':') 
		    values = value.gsub(/[\(\)]/, ' ').split(/[ ,;]/)

		    trimmed = Array.new
		    values.each { |val|
		       val = val.strip.downcase
		       trimmed << val if val.length > 0 && val !~ /^q=/
		    }

		    headers(name, *trimmed)
		end
	    end

	    merge(:query_param, CGI::parse(@uri.query)) if @uri.query

            has_stream = /POST|PUT/.match(@method)
            parsed = parse(stream, content_type?, content_length?) if has_stream 
	    case parsed
                when Hash then merge(:params, parsed)
                when Array then parsed.each{ |body| 
                    attachment[body.name] << body
                }
            end if parsed
          
	    headers("cookie").each { |cookie|
		CGI::Cookie::parse(cookie).each { |name, value|
		    @cookies[name] << value
		}
	    }
	end

	def content_length
	    "#{content_length?}".to_i 
	end

	def persistant?
	    con = connection?
	    con && con[0] == c?
	end
    end
end

if __FILE__ == $0 then
   RedWeb::HTTPServer.new.start
end


